import {Injectable} from '@angular/core';
import {AppContextService} from "./app-context.service";
import {HttpClient} from "@angular/common/http";
import {HttpService} from "./http.service";
import {Result} from "../model/restful";
import {NzMessageService} from "ng-zorro-antd/message";
import {RequestProgress, SingleUploadTask, UploadTask} from '../model/upload-task';
import {ActualUploadFile, UploadFileErrorReporter, UploadTaskResponse} from "../model/upload-dto";
import {Observable, Observer} from "rxjs";
import {NzNotificationService} from "ng-zorro-antd/notification";

const uploadTaskStarting = 1
const uploadTaskPreparing = 2
const uploadTaskPrepareFailure = 3
const uploadTaskRetry = 4
const uploadTaskRunning = 5
const uploadTaskPause = 6
const uploadTaskResume = 7
const uploadTaskCancel = 8
const uploadTaskSuccess = 9

interface SingleUploadFileInfo {
  filename: string
  size: number
  lastModified: number
}

interface UploadTaskInfoWrapper {
  path: string
  files: SingleUploadFileInfo[]
}

class MainTaskManger {

  public static createEmptyTask(uploadPath: string, files: FileList): UploadTask {
    const fileArray = UploadUtil.fileListToArray(files)
    return {
      contentLoading: false,
      title: '',
      tag: '',
      tagColor: 'blue',
      tagIcon: '',
      btnDisable: false,
      btnLoading: false,
      taskId: '',
      state: uploadTaskStarting,
      success: true,
      percent: 0,
      progressAutoCheckerId: -1,
      errorMsg: null,
      uploadPath: uploadPath,
      subTasks: [],
      error: null,
      nativeFiles: fileArray,
      totalSize: UploadUtil.calculateTotalFilesize(fileArray),
      availableCount: fileArray.length
    }
  }

  public static loadTaskInfoFromInfo(uploadService: UploadService, task: UploadTask) {
    MainTaskManger.clearTaskErrorFlag(task)
    if (task.taskId == '') {
      MainTaskManger.loadNewTaskFromServer(uploadService, task)
    } else {
      MainTaskManger.loadExistsTaskFromServer(uploadService, task)
    }
  }

  public static startAllTask(
    uploadService: UploadService, httpService: HttpService, task: UploadTask
  ) {
    let flag = true
    while (flag) {
      let count = 0
      task.subTasks.forEach(subTask =>  {
        if (subTask.state == uploadTaskRunning) {
          if (SingleTaskManger.sendChunk(uploadService, httpService, subTask)) {
            count++
          }
        }
      })
      if (count == 0) {
        flag = false
      }
    }

    task.subTasks.forEach(subTask => {
      if (subTask.state == uploadTaskRunning) {
        uploadService.onSubTaskStateChange(subTask, true)
      }
    })

    task.progressAutoCheckerId = MainTaskManger.startProgressAutoChecker(uploadService, task)
  }

  public static stopAllTask(uploadService: UploadService, task: UploadTask) {
    MainTaskManger.removeProgressAutoChecker(task)
    task.subTasks.forEach(subTask => {
      if (subTask.state == uploadTaskPause) {
        uploadService.onSubTaskStateChange(subTask)
      }
    })
  }

  public static removeProgressAutoChecker(task: UploadTask) {
    clearInterval(task.progressAutoCheckerId)
    task.progressAutoCheckerId = -1
  }

  public static onSubTaskStateChange(uploadService: UploadService, task: UploadTask) {
    // 什么都不做
    // if (task.availableCount == task.subTasks.length) { // 所有任务都已开始
    //   task.state = uploadTaskRunning
    //   uploadService.onMainTaskStateChange(task, true)
    // }
    // if (task.availableCount == 0) { // 所有子任务都已暂停
    //   task.state = uploadTaskPause
    //   uploadService.onMainTaskStateChange(task, true)
    // }
  }

  private static loadExistsTaskFromServer(uploadService: UploadService, task: UploadTask) {
    uploadService.pullMainTaskProgressFromServer(task.taskId).subscribe(
      MainTaskManger.defaultTaskRespHandler(uploadService, task)
    )
  }

  private static loadNewTaskFromServer(uploadService: UploadService, task: UploadTask) {
    const info = MainTaskManger.prepareUploadInfo(task)
    uploadService.submitUploadTask(info).subscribe(
      MainTaskManger.defaultTaskRespHandler(uploadService, task)
    )
  }

  private static defaultTaskRespHandler(
    uploadService: UploadService, task: UploadTask
  ): Partial<Observer<Result<UploadTaskResponse>>> {
    return {
      next(rsl: Result<UploadTaskResponse>) {
        if (rsl.success) {
          const resp = rsl.data!!
          if (resp.error == null) {
            MainTaskManger.onLoadTaskInfoSuccess(task, resp)
            task.state = uploadTaskRunning
          } else {
            MainTaskManger.onLoadTaskInfoFailure(task, resp.error)
            task.state = uploadTaskPrepareFailure
          }
        } else {
          task.errorMsg = rsl.message
          task.state = uploadTaskPrepareFailure
        }
      },
      error(e: any) {
        console.log('提交任务信息时遇到错误: ' + e)
        task.errorMsg = '网络错误'
        task.state = uploadTaskPrepareFailure
      },
      complete() {
        uploadService.onMainTaskStateChange(task)
      }
    }
  }

  private static startProgressAutoChecker(
    uploadService: UploadService, task: UploadTask
  ): number {
    return window.setInterval(() => {
      UploadUtil.calculateAndUpdateProgress(uploadService, task)
    }, 100)
  }

  private static clearTaskErrorFlag(task: UploadTask) {
    task.error = null
    task.errorMsg = ''
  }

  private static onLoadTaskInfoSuccess(task: UploadTask, resp: UploadTaskResponse) {
    task.subTasks = []
    task.taskId = resp.taskId
    const filenameMapper = MainTaskManger.getFilenameFileEntityMapper(task.nativeFiles)
    resp.files.forEach(actualFile => {
      const sub = SingleTaskManger.createTaskFromActualFile(actualFile, task, filenameMapper)
      task.subTasks.push(sub)
      if (actualFile.successCount == actualFile.progress.length) {
        sub.state = uploadTaskSuccess
        sub.percent = 100
      }
    })
  }

  private static onLoadTaskInfoFailure(task: UploadTask, error: UploadFileErrorReporter) {
    task.errorMsg = '容量不足'
    task.error = error
  }

  private static prepareUploadInfo(task: UploadTask): UploadTaskInfoWrapper {
    const infoArray: SingleUploadFileInfo[] = []
    task.nativeFiles.forEach(f => {
      infoArray.push({
        filename: f.name,
        size: f.size,
        lastModified: f.lastModified
      })
    })
    return {
      path: task.uploadPath,
      files: infoArray
    }
  }

  private static getFilenameFileEntityMapper(files: File[]): Map<string, File> {
    const map = new Map<string, File>()
    files.forEach(f => {
      map.set(f.name, f)
    })
    return map
  }
}

export class SingleTaskManger {

  public static createEmptyTask() {
    // todo 显示默认界面
  }

  public static createTaskFromActualFile(
    info: ActualUploadFile, mainTask: UploadTask, filenameMapper: Map<string, File>
  ): SingleUploadTask {
    const single: SingleUploadTask = {
      errMessage: '',
      btnDisable: false,
      btnIcon: '',
      deleteBtnVisible: false,
      btnLoading: false,
      state: uploadTaskRunning,
      parentRef: mainTask,
      progress: info.progress,
      percent: UploadUtil.calculatePercent(info.progress),
      filename: info.uploadFilename,
      realFilename: info.realFilename,
      chunkedSize: info.chunkedSize,
      file: filenameMapper.get(info.uploadFilename)!!,
      successCount: info.successCount,
      ctrl: {
       chunkSequence: [],
       reqs: new Map<number, RequestProgress>(),
      }
    }
    single.progress.forEach((item, chunkNum) => {
      if (!item) {
        single.ctrl.chunkSequence.push(chunkNum)
      }
    })
    return single
  }

  public static runTask(uploadService: UploadService, httpService: HttpService, single: SingleUploadTask) {
    while (SingleTaskManger.sendChunk(uploadService, httpService, single)) {
    }
  }

  public static sendChunk(
    uploadService: UploadService, httpService: HttpService, single: SingleUploadTask
  ): boolean {
    if (single.ctrl.chunkSequence.length == 0) {
      return false
    } else {
      const nextChunkNum = single.ctrl.chunkSequence.shift()!!
      const fd = SingleTaskManger.prepareChunkFormData(single, nextChunkNum)
      const xhr = SingleTaskManger.prepareUploadFileChunkRequest(
        uploadService, httpService, single, nextChunkNum
      )
      xhr.send(fd)
      single.ctrl.reqs.set(nextChunkNum, {
        req: xhr,
        percent: 0
      })
      return true
    }
  }

  public static pauseTask(uploadService: UploadService, single: SingleUploadTask) {
    single.ctrl.chunkSequence = []
    single.ctrl.reqs.forEach((req) => {
      if (req != null) {
        req.req.abort()
      }
    })
    single.ctrl.reqs.clear()
  }

  public static loadProgressFromServer(uploadService: UploadService, single: SingleUploadTask) {
    SingleTaskManger.clearErrorFlag(single)
    uploadService.pullSingleTaskProgressFromServer(single.parentRef.taskId, single.filename).subscribe({
      next(rsl) {
        if (rsl.success) {
          const info = rsl.data!!
          single.progress = info.progress
          single.realFilename = info.realFilename
          single.successCount = info.successCount
          single.percent = UploadUtil.calculatePercent(single.progress)
          single.chunkedSize = info.chunkedSize
          if (info.done) {
            single.state = uploadTaskSuccess
          } else {
            single.state = uploadTaskRunning
          }
        } else {
          single.errMessage = rsl.message
          single.state = uploadTaskPrepareFailure
        }
      },
      error(e) {
        console.log('从服务器拉取任务进度时遇到异常: ' + e)
        single.errMessage = '网络错误'
        single.state = uploadTaskPrepareFailure
      },
      complete() {
        uploadService.onSubTaskStateChange(single)
      }
    })
  }

  private static clearErrorFlag(single: SingleUploadTask) {
    single.errMessage = ''
  }

  private static prepareChunkFormData(single: SingleUploadTask, chunkNum: number): FormData {
    const fd = new FormData()
    fd.append('taskId', single.parentRef.taskId)
    fd.append('filename', single.filename)
    fd.append('chunkedSize', single.chunkedSize.toString())
    fd.append('chunkNum', chunkNum.toString())

    const startIdx = (chunkNum * single.chunkedSize)
    const chunkedSize = UploadUtil.calculateChunkedSize(single.file.size, single.chunkedSize, chunkNum)
    fd.append('chunk', single.file.slice(startIdx, startIdx + chunkedSize))

    return fd
  }

  private static prepareUploadFileChunkRequest(
    uploadService: UploadService, server: HttpService,
    single: SingleUploadTask, chunkNum: number
  ): XMLHttpRequest {
    const xhr = new XMLHttpRequest()
    xhr.onprogress = (event) => {
      SingleTaskManger.updateTaskProgress(single, chunkNum, event)
    }

    xhr.onreadystatechange = () => {
      if (xhr.readyState == 4) {
        if (xhr.status != 200) {
          single.state = uploadTaskPrepareFailure
          uploadService.onSubTaskStateChange(single)
        }
      }
    }
    xhr.open('PATCH', server.baseUrl() + '/upload/')
    return xhr
  }

  private static updateTaskProgress(single: SingleUploadTask, chunkNum: number, event: ProgressEvent) {
    const chunkReq = single.ctrl.reqs.get(chunkNum)
    if (chunkReq != null) {
      chunkReq.percent = event.loaded / (event.total ?? 0)
    }
  }
}

export class UploadUtil {
  public static calculateTotalFilesize(files: File[]): number {
    let total = 0
    files.forEach(f => {
      total += f.size
    })
    return total
  }

  public static fileListToArray(fileList: FileList): File[] {
    const fileArray: File[] = []
    for (let i = 0; i < fileList.length; i++) {
      fileArray.push(fileList.item(i)!!)
    }
    return fileArray
  }

  public static calculatePercent(progress: boolean[]): number {
    let denominator = progress.length // 分母
    let numerator = 0 // 分子
    progress.forEach(item => {
      if (item) {
        numerator++
      }
    })
    if (numerator == 0) return 0
    return numerator / denominator
  }

  public static calculateChunkedSize(
    filesize: number, chunkedSize: number, chunkNum: number
  ): number {
    if (filesize <= chunkedSize) return filesize
    const rest = filesize - (chunkNum * chunkedSize)
    return Math.min(rest, chunkedSize)
  }

  public static calculateAndUpdateProgress(uploadService: UploadService, task: UploadTask) {
    let denominator = task.subTasks.length * 100
    let numerator = 0
    task.subTasks.forEach(subTask => {
      const subTaskPercent = UploadUtil.calculateSingleTaskAndUpdateProgress(uploadService, subTask)
      numerator += subTaskPercent
    })
    task.percent = parseFloat(((numerator / denominator) * 100).toFixed(2))
    if (task.percent >= 100) {
      task.state = uploadTaskSuccess
      uploadService.onMainTaskStateChange(task)
    }
  }

  // 返回 0~100
  private static calculateSingleTaskAndUpdateProgress(
    uploadService: UploadService, single: SingleUploadTask
  ): number {
    if (single.percent >= 100 || single.successCount == single.progress.length) {
      return 100
    }
    if (single.state != uploadTaskPause) {
      return single.percent
    }

    let denominator = single.progress.length
    let numerator = single.successCount
    single.ctrl.reqs.forEach(req => {
      numerator += req.percent
    })
    single.percent = parseFloat(((numerator / denominator) * 100).toFixed(2))
    if (single.percent >= 100) {
      UploadUtil.tryNotifyFileUploadEvent(uploadService, single)
      single.state = uploadTaskSuccess
      uploadService.onSubTaskStateChange(single)
    }
    return single.percent
  }

  private static tryNotifyFileUploadEvent(uploadService: UploadService, single: SingleUploadTask) {
    const task = single.parentRef
    uploadService.uploadTasks.forEach(t => {
      if (t === task) {
        uploadService.notifyFileUploadSuccess(single.filename)
      }
    })
  }
}

@Injectable({
  providedIn: 'root'
})
export class UploadService {

  uploadTasks: UploadTask[] = []

  constructor(
    private message: NzMessageService,
    private ctx: AppContextService,
    private httpClient: HttpClient,
    private server: HttpService,
    private notification: NzNotificationService
  ) {
  }

  registerUploadTask(uploadPath: string, fileList: FileList): UploadTask {
    return MainTaskManger.createEmptyTask(uploadPath, fileList)
  }

  onMainTaskStateChange(task: UploadTask, justUpdateView: boolean = false) {
    console.log('状态改变:' + task.state)
    switch (task.state) {
      case uploadTaskStarting: {
        task.title = '开始上传'
        task.tag = '未开始'
        task.tagIcon = 'caret-right'
        task.tagColor = 'default'
        task.btnDisable = false
        task.btnLoading = false
        task.state = uploadTaskPreparing
        break
      }
      case uploadTaskPreparing: {
        task.contentLoading = true
        task.title = '准备中'
        task.tag = '准备数据'
        task.tagColor = 'processing'
        task.tagIcon = 'sync'
        task.btnDisable = true
        task.btnLoading = true
        MainTaskManger.loadTaskInfoFromInfo(this, task)
        break
      }
      case uploadTaskPrepareFailure: {
        task.contentLoading = false
        task.title = '重试'
        task.tag = '失败'
        task.tagIcon = 'close-circle'
        task.tagColor = 'error'
        task.btnDisable = false
        task.btnLoading = false
        task.state = uploadTaskRetry
        break
      }
      case uploadTaskRetry: {
        task.btnLoading = true
        task.btnDisable = true
        task.state = uploadTaskPreparing
        this.onMainTaskStateChange(task)
        break
      }
      case uploadTaskRunning: {
        task.contentLoading = false
        task.title = '全部暂停'
        task.tag = '上传中'
        task.tagIcon = 'caret-right'
        task.tagColor = 'processing'
        task.btnLoading = false
        task.btnDisable = false
        task.state = uploadTaskPause
        if (!justUpdateView) {
          MainTaskManger.startAllTask(this, this.server, task)
        }
        break
      }
      case uploadTaskPause: {
        task.title = '全部开始'
        task.tag = '已暂停'
        task.tagIcon = 'clock-circle'
        task.tagColor = 'default'
        task.btnLoading = false
        task.btnDisable = false
        task.state = uploadTaskResume
        if (!justUpdateView) {
          MainTaskManger.stopAllTask(this, task)
        }
        break
      }
      case uploadTaskResume: {
        task.state = uploadTaskPreparing
        this.onMainTaskStateChange(task)
        break
      }
      case uploadTaskCancel: {
        task.title = '已取消'
        task.tag = '任务取消'
        task.tagIcon = 'close-circle'
        task.btnLoading = false
        task.btnDisable = true
        MainTaskManger.removeProgressAutoChecker(task)
        break
      }
      case uploadTaskSuccess: {
        task.title = '已完成'
        task.tag = '完成'
        task.tagIcon = 'check-circle'
        task.tagColor = 'success'
        task.btnLoading = false
        task.btnDisable = true
        MainTaskManger.removeProgressAutoChecker(task)
        break
      }
    }
  }

  onSubTaskStateChange(single: SingleUploadTask, justUpdateView: boolean = false) {
    switch (single.state) {
      case uploadTaskStarting: {
        single.btnIcon = 'caret-right'
        single.btnDisable = false
        single.btnLoading = false
        single.state = uploadTaskPreparing
        break
      }
      case uploadTaskPreparing: {
        single.btnLoading = true
        single.btnDisable = true
        SingleTaskManger.loadProgressFromServer(this, single)
        break
      }
      case uploadTaskPrepareFailure: {
        single.btnIcon = 'reload'
        single.btnDisable = false
        single.btnLoading = false
        single.state = uploadTaskRetry
        break
      }
      case uploadTaskRetry: {
        single.btnDisable = true
        single.btnLoading = true
        single.state = uploadTaskPreparing
        this.onSubTaskStateChange(single)
        break
      }
      case uploadTaskRunning: {
        single.btnLoading = false
        single.btnDisable = false
        single.errMessage = ''
        single.btnIcon = 'pause'
        if (!justUpdateView) {
          SingleTaskManger.runTask(this, this.server, single)
        }

        single.state = uploadTaskPause
        break
      }
      case uploadTaskPause: {
        single.btnLoading = false
        single.btnLoading = false
        single.btnIcon = 'caret-right'
        SingleTaskManger.pauseTask(this, single)
        single.state = uploadTaskResume
        break
      }
      case uploadTaskResume: {
        single.state = uploadTaskPreparing
        this.onSubTaskStateChange(single)
        break
      }
      case uploadTaskCancel: {
        single.btnLoading = false
        single.btnDisable = true
        single.btnIcon = 'close'
        break
      }
      case uploadTaskSuccess: {
        single.btnLoading = false
        single.btnDisable = true
        single.btnIcon = 'check'
        break
      }
    }
  }

  addHistoryTask(task: UploadTask) {
    this.uploadTasks.push(task)
  }

  // 检查是否具有此目录的上传权限
  checkUploadPerm(callback: () => void) {
    const ref = this
    const path = {
      path: ref.ctx.currentPath
    }

    const handle = {
      next(r: Result<any>) {
        if (r.success) {
          callback()
        } else {
          ref.message.error(r.message)
        }
      },
      error(e: any) {
        console.log(e)
        ref.message.error('网络错误')
      }
    }

    this.httpClient.post<Result<any>>(this.server.baseUrl() + "/upload/checkPerm", path)
      .subscribe(handle)
  }

  public submitUploadTask(taskInfo: UploadTaskInfoWrapper): Observable<Result<UploadTaskResponse>> {
    return this.httpClient.post<Result<UploadTaskResponse>>(this.server.baseUrl() + '/upload/checkCap', taskInfo)
  }

  public pullMainTaskProgressFromServer(taskId: string): Observable<Result<UploadTaskResponse>> {
    return this.httpClient.post<Result<UploadTaskResponse>>(
      this.server.baseUrl() + '/upload/main-progress', {taskId: taskId})
  }

  public pullSingleTaskProgressFromServer(
    taskId: string, filename: string
  ): Observable<Result<ActualUploadFile>> {
    const jsonBody = {
      taskId: taskId,
      filename: filename
    }
    return this.httpClient.post<Result<ActualUploadFile>>(
      this.server.baseUrl() + '/upload/sub-progress', jsonBody
    )
  }

  public notifyFileUploadSuccess(filename: string) {
    this.notification.create('success', '文件上传成功', `${filename}`, {
      nzPlacement: 'topRight',
      nzDuration: 4000
    })
  }
}
